<?php
class User_service extends CI_Model
{

    const USER_TOKEN_PRE = 'user_token_%s';
    const USER_VCODE_PRE = 'user_vcode_%s';
    const USER_HEAD_PIC = 'http://image.hanlingsha.love/lwa/headpic/B85A31EB64F482EF44CBDEB6FF49C1C5.jpg';
    const REG_GIVE_COUPON = 2000;

    public function __construct()
    {
        parent::__construct();
        $this->load->library('redis_proxy');
        $this->load->model('user_model');
        $this->load->model('sms_model');
        $this->load->model('book_shelf_model');
        $this->load->model('novel_model');
        $this->load->model('chapter_model');
        $this->load->model('coupon_log_model');
        $this->load->model('consumption_log_model');
    }

    /**
     * Get a vcode before do login
     *
     * @param number $user_phone
     * @param err $err
     * @return mixed
     */
    public function getVcode($user_phone, &$err)
    {
        if (empty($user_phone) || strlen($user_phone) < 1 || ! is_numeric($user_phone))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数异常',
            ];
            message_logger('error', sprintf('params error, user phone is %s', $user_phone));
            return false;
        }
        if ( ! $this->_checkUserExist($user_phone))
        {
            $err = [
                'errorCode'     => NO_USER,
                'errorMessage'  => '用户不存在',
            ];
            return false;
        }
        $key = sprintf(self::USER_VCODE_PRE, $user_phone);
        $vcode = rand(10000, 99999);
        list($set, $redis_err) = $this->redis_proxy->set($key, $vcode);
        if (! is_null($redis_err))
        {
            $err = [
                'errorCode'     => SYSTEM_ERR,
                'errorMessage'  => '系统异常',
            ];
            message_logger('error', sprintf('redis error , %s', $redis_err));
            return false;
        }
        return $vcode;
    }

    /**
     * check user password and return token
     *
     * @param number $user_phone
     * @param number $vcode
     * @param string $hash_pwd
     *                  FE create a hash string by password and "getVocode" returned value
     *                  md5(md5(USER_REAL_PASSWORD) + vcode)
     * @param err $err
     * @return mixed
     */
    public function login($user_phone, $hash_pwd, &$err)
    {
        if (empty($user_phone) || empty($hash_pwd))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数异常',
            ];
            message_logger('error', sprintf('params error, params are [%s][%s][%s]', $user_phone, $vcode, $hash_pwd));
            return false;
        }
        list($vcode, $_redis_err) = $this->redis_proxy->get(sprintf(self::USER_VCODE_PRE, $user_phone));
        $user_password = $this->_getUserPwd($user_phone);
        $hash_again_password = md5($user_password . $vcode);
        if ($hash_again_password != $hash_pwd)
        {
            $err = [
                'errorCode'     => ERROR_PASSWORD,
                'errorMessage'  => '密码错误',
            ];
            $this->_cleanVcode($user_phone);
            return false;
        }
        $this->_cleanVcode($user_phone);
        $user_info = $this->getUserInfoByPhone($user_phone);
        $token = random(32, 'chars');
        $user_id = $user_info['user_id'];
        $result = [
            'userId'       => $user_id,
            'token'        => $token,
        ];
        $user_token_key = sprintf(self::USER_TOKEN_PRE, $user_id);
        $this->redis_proxy->set($user_token_key, $token);
        return $result;
    }

    /**
     * check user login status
     *
     * @param string $user_id
     * @param string $token
     * @param err $err
     * @return boolean
     */
    public function checkUserLogin($user_id, $token, &$err)
    {
        $key = sprintf(self::USER_TOKEN_PRE, $user_id);
        list($redis_token, $redis_err) = $this->redis_proxy->get($key);
        if (! is_null($redis_err))
        {
            $err = [
                'errorCode'     => SYSTEM_ERR,
                'errorMessage'  => '系统异常',
            ];
            message_logger('error', sprintf('redis error , %s', $redis_err));
            return false;
        }
        if (empty($redis_token))
        {
            $err = [
                'errorCode'     => NO_LOGIN,
                'errorMessage'  => '用户未登录',
            ];
            return false;
        }
        if ($redis_token != $token)
        {
            $err = [
                'errorCode'     => NO_LOGIN,
                'errorMessage'  => '登录过期',
            ];
            return false;
        }
        return true;
    }

    /**
     * User reg
     *      auto login when user reg success
     *
     * @param number $user_phone
     * @param string $password
     * @param string $user_name
     * @param err $err
     * @return mixed
     */
    public function register($user_phone, $password, &$err)
    {
        if (IS_OPEN_REG === false)
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '注册未开放',
            ];
            return false;
        }
        if (empty($user_phone) || strlen($user_phone) < 1 || empty($password))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数异常',
            ];
            message_logger('error', sprintf('params error, params are [%s][%s]', $user_phone, $password));
            return false;
        }
        $check_exist = $this->_checkUserExist($user_phone);
        if ($check_exist)
        {
            $err = [
                'errorCode'     => USER_DUC,
                'errorMessage'  => '用户已经存在',
            ];
            return false;
        }
        $user_id = $this->_createUser($user_phone, $password);
        $this->_CouponFromRegLog($user_id);
        $token = random(32);
        $user_token_key = sprintf(self::USER_TOKEN_PRE, $user_id);
        $this->redis_proxy->set($user_token_key, $token);
        $result = [
            'userId'        => $user_id,
            'token'         => $token,
        ];
        return $result;
    }

    /**
     * get user info by user_phone
     *
     * @param number $user_phone
     * @return array
     */
    public function getUserInfoByPhone($user_phone)
    {
        $row = $this->user_model->findOne([
            'fields'        => 'user_id, user_name',
            'where'         => [
                'phone' => $user_phone,
            ],
        ]);
        return $row;
    }

    public function sendSms($phone, &$err)
    {
        if (IS_OPEN_REG === false)
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '注册未开放',
            ];
            return false;
        }
        if (empty($phone) || ! is_numeric($phone))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数错误',
            ];
            message_logger('error', sprintf('phone invaild, [%s]', $phone));
            return false;
        }
        if ($this->countSmsSendTimeToday($phone) >= $this->sms_model->max_send_day)
        {
            $err = [
                'errorCode'     => SMS_ERR,
                'errorMessage'  => '今日次数已用尽',
            ];
            return false;
        }
        if ($this->_checkUserExist($phone))
        {
            $err = [
                'errorCode'     => SMS_ERR,
                'errorMessage'  => '此手机号已注册',
            ];
            return false;
        }
        $code = rand(1000, 9999);
        $sms_id = $this->sms_model->getUuid();
        $this->sms_model->insert([
            'sms_id'    => $sms_id,
            'code'      => $code,
            'status'    => $this->sms_model->sms_status['sended'],
            'send_time' => time(),
            'phone_number' => $phone,
            'type'          => $this->sms_model->sms_type['reg'],
        ]);
        $send_url = "http://v.juhe.cn/sms/send";
        $request_params = [
            'key'       => $_SERVER['SMS_APP_KEY'],
            'mobile'    => $phone,
            'tpl_id'    => '85973', // 短信模板ID 聚合控制台查看
            'tpl_value' => sprintf('#code#=%s', $code) // 模板变量
        ];
        $result = curlRequest($send_url, 'POST', $request_params);
        message_logger('debug', 'show me sms send result is ' . var_export($result, true));
        if ( isset($result['error_code']) && $result['error_code'] === 0)
        {
            // if Read - Write, there must use master database
            $this->sms_model->update([
                'where'     => [
                    'sms_id'        => $sms_id,
                ],
            ], [
                'status'    => $this->sms_model->sms_status['send_success'],
            ]);
            return true;
        }
        else
        {
            $err = [
                'errorCode'     => SMS_ERR,
                'errorMessage'  => '网络错误',
            ];
            message_logger('error', sprintf('send sms error, %s', var_export($result, true)));
            return false;
        }
    }

    /**
     * Check sms code
     *
     * @param string $phone
     * @param number $code
     * @param err $err
     * @return boolean
     */
    public function checkRegSmsCode($phone, $code, &$err)
    {
        if (IS_OPEN_REG === false)
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '注册未开放',
            ];
            return false;
        }
        if (empty($phone) || empty($code))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数错误',
            ];
            message_logger('error', sprintf('phone invaild, [%s]', $phone));
            return false;
        }
        $sms_row = $this->sms_model->findOne([
            'fields'    => 'sms_id, code, available_time, send_time',
            'where'     => [
                'phone_number'  => $phone,
                'type'          => $this->sms_model->sms_type['reg'],
                'status'        => $this->sms_model->sms_status['send_success'],
            ],
        ]);
        if (empty($sms_row))
        {
            $err = [
                'errorCode'     => SMS_CHECK_ERR,
                'errorMessage'  => '验证码不存在',
            ];
            message_logger('debug', sprintf('sms not found , phone [%s], sms [%]', $phone, $code));
            return false;
        }
        if (time() - $sms_row['send_time'] > $sms_row['available_time'])
        {
            $err = [
                'errorCode'     => SMS_CHECK_ERR,
                'errorMessage'  => '验证码已过期',
            ];
            $this->sms_model->update([
                'where'     => [
                    'sms_id'        => $sms_row['sms_id'],
                ],
            ], [
                'status'    => $this->sms_model->sms_status['overdue'],
            ]);
            return false;
        }
        if ($code != $sms_row['code'])
        {
            $err = [
                'errorCode'     => SMS_CHECK_ERR,
                'errorMessage'  => '验证码错误',
            ];
            return false;
        }
        return true;
    }

    public function countSmsSendTimeToday($phone)
    {
        $time = time();
        $count = $this->sms_model->findOne([
            'fields'        => 'count(sms_id) as cn',
            'where'         => [
                'phone_number'  => $phone,
                'send_time >'   => strtotime('Y-m-d', $time),
                'send_time <'   => $time,
            ],
            'where_in'      => [
                'status'        => [
                    $this->sms_model->sms_status['send_success'],
                    $this->sms_model->sms_status['used'],
                    $this->sms_model->sms_status['overdue'],
                ],
            ],
        ]);
        return $count['cn'];
    }

    public function getUserInfoById($user_id)
    {
        if (empty($user_id))
        {
            message_logger('error', 'user id is empty');
            return [];
        }
        $user = $this->user_model->findOne([
            'fields'        => 'user_name, role, user_id, phone',
            'where'         => ['user_id'   => $user_id],
        ]);
        return $user;
    }

    /**
     * logout / clean user token from redis
     *
     * @param string $user_id
     * @return boolean
     */
    public function logout($user_id)
    {
        $user_token_key = sprintf(self::USER_TOKEN_PRE, $user_id);
        $this->redis_proxy->del($user_token_key);
        return true;
    }

    public function addShelf($user_id, $novel_id, &$err)
    {
        if (empty($user_id) || empty($novel_id))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数错误',
            ];
            return false;
        }
        $row = $this->book_shelf_model->findOne([
            'fields'    => 'shelf_id',
            'where'     => [
                'user_id'   => $user_id,
                'novel_id'  => $novel_id,
            ],
        ]);
        if ( ! empty($row))
        {
            $err = [
                'errorCode'     => NOVEL_SHELF_DUC,
                'errorMessage'  => '书架已存在',
            ];
            return false;
        }
        $cunt = $this->book_shelf_model->findOne([
            'fields'    => 'count(*) as cnt',
            'where'     => [
                'user_id'   => $user_id,
            ],
        ])['cnt'] ?? 0;
        if ($cunt > 20)
        {
            $err = [
                'errorCode'     => SYSTEM_ERR,
                'errorMessage'  => '书架书籍过多',
            ];
            return false;
        }
        $insert_data = [
            'shelf_id'  => $this->book_shelf_model->getUuid(),
            'user_id'   => $user_id,
            'novel_id'  => $novel_id,
            'add_time'  => time(),
        ];
        $this->book_shelf_model->insert($insert_data);
        return true;
    }

    public function deleteShelf($user_id, $novel_id, &$err)
    {
        if (empty($user_id) || empty($novel_id))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数错误',
            ];
            return false;
        }
        $this->book_shelf_model->del([
            'where' => [
                'user_id'   => $user_id,
                'novel_id'  => $novel_id,
            ],
        ]);
        return false;
    }

    public function getUserShelf($user_id)
    {
        if (empty($user_id))
        {
            return [];
        }
        $shelf_novels = $this->book_shelf_model->findAll([
            'fields'        => 'user_id, shelf_id, novel_id',
            'where'         => [
                'where' => [
                    'user_id'   => $user_id,
                ],
            ],
        ]);
        if (empty($shelf_novels))
        {
            return [];
        }
        $this->_fillNovels($shelf_novels);
        return $shelf_novels;
    }

    public function devCoupon($user_id)
    {
        $sql = 'update users set coupon = coupon + 2000 where user_id = ?';
        $this->user_model->db->query($sql, [$user_id]);
        $coupon_log_id = $this->coupon_log_model->getUuid();
        $this->coupon_log_model->insert([
            'coupon_log_id' => $coupon_log_id,
            'user_id'       => $user_id,
            'type'          => 'DEV',
            'coupon'        => 2000,
            'add_time'      => time(),
        ]);
    }

    public function getUserCenter($user_id, $person_id, &$err)
    {
        if (empty($user_id) && empty($person_id))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数错误',
            ];
            return false;
        }

        if ($user_id == $person_id)
        {
            $user_info = $this->_findUserSelfInfo($user_id);
            return toCamelCaseKey($user_info);
        }
        $user_info = $this->_findOtherUserInfo($person_id);
        return toCamelCaseKey($user_info);
    }

    public function changeUserInfo($update_data, $user_id, &$err)
    {
        if (empty($update_data) || empty($user_id))
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '参数错误',
            ];
            return false;
        }

        $can_change = ['user_name', 'headpic'];
        foreach($update_data as $key => $value)
        {
            if ( ! in_array($key, $can_change))
            {
                $err = [
                    'errorCode'     => PARAM_ERR,
                    'errorMessage'  => '非法项目',
                ];
                message_logger('error', sprintf('user [%s] wanna change column [%s], all params is [%s]', $user_id, $key, var_export($update_data, true)));
                return false;
            }
            if (empty($value))
            {
                $err = [
                    'errorCode'     => PARAM_ERR,
                    'errorMessage'  => '修改值不能为空',
                ];
                return false;
            }
        }
        if (mb_strlen($update_data['user_name']) > 20)
        {
            $err = [
                'errorCode'     => PARAM_ERR,
                'errorMessage'  => '名称过长',
            ];
            return false;
        }
        $this->user_model->update([
            'where' => [
                'user_id'   => $user_id,
            ]
        ], $update_data);
        return true;
    }

    public function getConsumptionLog($user_id, $page, &$err)
    {
        if (empty($user_id))
        {
            $err = [
                'errorCode'     => NO_LOGIN,
                'errorMessage'  => '未登录',
            ];
            return false;
        }
        if (empty($page))
        {
            $page = 1;
        }
        $consumption_log = $this->consumption_log_model->findAll([
            'fields'        => 'consumption_log_id, user_id, novel_id, chapter_id, add_time, coupon',
            'where'         => [
                'user_id'   => $user_id,
            ],
            'limit'         => 50,
            'offset'        => ($page - 1) * 50,
        ]);
        if (empty($consumption_log))
        {
            return [];
        }
        $this->_fillConsumptionLog($consumption_log);
        return toCamelCaseKey($consumption_log);
    }

    private function _fillConsumptionLog(&$consumption_log)
    {
        $novel_ids = array_column($consumption_log, 'novel_id');
        $chapter_ids = array_column($consumption_log, 'chapter_id');
        $novels = $this->novel_model->findAll([
            'fields'        => 'novel_id, novel_name',
            'where_in'      => [
                'novel_id'  => $novel_ids,
            ]
        ]);
        $chapters = $this->chapter_model->findAll([
            'fields'        => 'chapter_id, chapter_name',
            'where_in'      => [
                'chapter_id'        => $chapter_ids,
            ]
        ]);
        $novel_map = array_column($novels, null, 'novel_id');
        $chapter_map = array_column($chapters, null, 'chapter_id');
        array_walk($consumption_log, function (&$row) use ($novel_map, $chapter_map) {
            $novel_id = $row['novel_id'];
            $chapter_id = $row['chapter_id'];
            $row['novel_name'] = $novel_map[$novel_id]['novel_name'] ?? '';
            $row['chapter_name'] = $chapter_map[$chapter_id]['chapter_name'] ?? '';
        });
    }

    private function _findUserSelfInfo($user_id)
    {
        $user_info = $this->user_model->findOne([
            'fields'        => 'user_id, user_name, coupon, headpic',
            'where'         => [
                'user_id'   => $user_id,
            ]
        ]);
        return $user_info;
    }

    private function _findOtherUserInfo($user_id)
    {
        $user_info = $this->user_model->findOne([
            'fields'        => 'user_id, user_name, coupon, headpic',
            'where'         => [
                'user_id'   => $user_id,
            ]
        ]);
        return $user_info;
    }

    private function _fillNovels(&$novels)
    {
        if (empty($novels))
        {
            return;
        }
        $novel_ids = array_column($nvoels, 'novel_id');
        $novels_info = $this->novel_model->findAll([
            'fields'        => 'novel_id, novel_name, cover_url',
            'where_in'      => [
                'novel_id'  => $novel_ids,
            ],
            'where'         => [
                'deleted'   => 'N',
                'hide'      => 'N',
                'publish'   => 'Y',
            ],
        ]);
        if (empty($novels_info))
        {
            $novels = [];
            return;
        }
        $novels_map = array_column($novels_info, null, 'novel_id');
        array_walk($novels, function(&$row) use ($novels_map){
            $novel_id = $row['novel_id'];
            $row['novel_info'] = $novels_map[$novel_id] ?? [];
        });
    }

    private function _CouponFromRegLog($user_id)
    {
        $coupon_log_id = $this->coupon_log_model->getUuid();
        $this->coupon_log_model->insert([
            'coupon_log_id' => $coupon_log_id,
            'user_id'       => $user_id,
            'type'          => $this->coupon_log_model->give_type['reg'],
            'coupon'        => self::REG_GIVE_COUPON,
            'add_time'      => time(),
        ]);
    }

    /**
     * get user password(hashed) by phone
     *
     * @param number $user_phone
     * @return mixed
     */
    private function _getUserPwd($user_phone)
    {
        $row = $this->user_model->findOne([
            'fields'    => 'password',
            'where'     => [
                'phone' => $user_phone
            ],
        ]);
        return $row['password'] ?? null;
    }

    /**
     * check user is already in system?
     * return false : not found
     * rerurn true : found
     *
     * @param number $user_phone
     * @return boolean
     */
    private function _checkUserExist($user_phone)
    {
        $row = $this->user_model->findOne([
            'fields'    => 'user_id',
            'where'     => [
                'phone' => $user_phone
            ],
        ]);
        return ! empty($row);
    }

    private function _cleanVcode($user_phone)
    {
        $key = sprintf(self::USER_VCODE_PRE, $user_phone);
        $this->redis_proxy->del($key);
    }

    private function _make_user_name_by_phone($user_phone)
    {
        $start = substr($user_phone, 0, 3);
        $end = substr($user_phone, 7, 4);
        return sprintf('%s****%s', $start, $end);
    }

    public function createDevUser($user_phone, $password)
    {
        $this->_createUser($user_phone, $password, 'R');
    }

    public function createUser($user_phone, $password)
    {
        $this->_createUser($user_phone, $password, 'U');
        $this->_CouponFromRegLog($user_id);
    }


    private function _createUser($user_phone, $password, $role = 'U')
    {
        $user_name = $this->_make_user_name_by_phone($user_phone);
        $user_id = $this->user_model->getUuid();
        $this->user_model->insert([
            'user_id'       => $user_id,
            'user_name'     => $user_name,
            'password'      => md5($password),
            'regtime'       => time(),
            'role'          => $role,
            'headpic'       => self::USER_HEAD_PIC,
            'phone'         => $user_phone,
            'coupon'        => self::REG_GIVE_COUPON,
        ]);
        return $user_id;
    }
}